package de.axone.wash;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Set;

import org.json.JSONException;
import org.json.JSONObject;

import de.axone.wash.WashTools.TypeValue;


/**
 * Normal Variables:
 * var="string		// Escapes: \\ \n \00, Alle Zeichen 0x00-0x19, 0x7f-0xff
 * var=i1000
 * var=f7.5
 * var=bTRUE|bFALSE
 * var=dKLSADKFHALKHLKASDLKF==	//bin64
 * 
 * Arrays
 * var.0="Element 0 
 * var.1="Element 1
 * ...
 * 
 * @author flo
 */


public class DefaultWash implements Wash{
	
	private HashMap<String, DefaultWash.Field> fields = new HashMap<String, DefaultWash.Field>();
	private LinkedList<DefaultWash.Field> indexedFields = new LinkedList<DefaultWash.Field>();
	
	private class Field {
		
		private Object value;
		private String name;
		private Type type;
		
		private Field( String name, Type type ){
			
			this.name = name;
			this.type = type;
		}
	}
	
	
	// --- creation ---------------------------------------------------
	
	public void addField( String name, Type type ) throws DuplicateEntryException {
		
		if( fields.containsKey( name ) )
			throw new DuplicateEntryException();
		
		Field field = new Field( name, type );
		fields.put( name, field );
		indexedFields.addLast( field );
	}
	
	public void addField( String name, Type type, Object value ) 
	throws DuplicateEntryException, WrongTypeException, NotFoundException {
		
		if( value != null ){
			checkType( type, value );
			addField( name, type );
			set( name, value );
		}else{
			addField( name, type );
		}
	}
	
	public void removeField( String name ) throws NotFoundException {
		
		if( ! fields.containsKey( name ) )
			throw new NotFoundException();
		
		Field field = fields.get( name );
		
		fields.remove( name );
		indexedFields.remove( field );
	}
	
	public boolean hasField( String name ) {
		
		return fields.containsKey( name );
	}
	
	
	// --- GETTER/SETTER and friends ----------------------------------
	
	public Set<String> getFieldNames(){
		
		return fields.keySet();
	}
	
	private void checkType( Type type, Object value ) throws WrongTypeException {
		
		if( type == null && value == null )
			return;
		
		switch( type ){
		
		case STRING:
			if( ! ( value instanceof String ) )
				throw new WrongTypeException();
			break;
		
		case INTEGER:
			
			if( ! ( 
					( value instanceof Long ) || 
					( value instanceof Integer ) || 
					( value instanceof Short ) ||
					( value instanceof Byte )
			) ){
				
				throw new WrongTypeException();
			}
			break;
		
		case FLOAT:
			
			if( ! ( 
					( value instanceof Double ) || 
					( value instanceof Float )
			) )
				throw new WrongTypeException();
			break;
			
		case BOOLEAN:
			
			if( ! ( value instanceof Boolean ) )
				throw new WrongTypeException();
			break;
			
		case DATA:
			
			if( ! ( value instanceof byte[] ) )
				throw new WrongTypeException();
			break;
		}
	}
	
	private Field getField( String name ) throws NotFoundException {
		
		if( ! fields.containsKey( name ) )
			throw new NotFoundException();
		
		return fields.get( name );
	}
	
	private Field getFieldChecked( String name, Type type ) throws NotFoundException, WrongTypeException {
		
		Field v = getField( name );
		
		if( v.type != type && v.value != null ){
			
			throw new WrongTypeException();
		}
		return v;
	}
	
	public Type getType( String name ) throws NotFoundException {

		return getField( name ).type;
	}

	
	// typed GETTER/SETTER Access
	
	public void set( String name, Object value )
	throws NotFoundException, WrongTypeException {
		
		if( ! fields.containsKey( name ) )
			throw new NotFoundException();
		
		Field v = fields.get( name );
		
		if( value == null ) {
			
			v.value = null;
			return;
		}
		
		switch( v.type ){
		
		case STRING:
			// Failfast with Classcast-Exception
			if( value instanceof String ) {
				v.value = value;	
			} else {
				throw new WrongTypeException();
			}
			break;
			
		case INTEGER:
			Long l;
			if( value instanceof Byte ) {
				l = new Long( (Byte)value );
			} else if( value instanceof Short ) {
				l = new Long( (Short)value );
			} else if( value instanceof Integer ) {
				l = new Long( (Integer)value );
			} else if( value instanceof Long ) {
				l = (Long) value;
			} else{
				throw new WrongTypeException();
			}
			v.value = l;
			break;
		
		case FLOAT:
			Double d;
			if( value instanceof Float ) {
				d = new Double( (Float)value );
			} else if( value instanceof Double ) {
				d = (Double)value;
			} else {
				throw new WrongTypeException();
			}
			v.value = d;
			break;
			
		case BOOLEAN:
			if( value instanceof Boolean ) {
				v.value = value;	
			} else {
				throw new WrongTypeException();
			}
			break;
			
		case DATA:
			if( value instanceof byte[] ) {
				v.value = value;	
			} else {
				throw new WrongTypeException();
			}
			break;
		}
	}
	
	public void setString( String name, String value ) throws NotFoundException, WrongTypeException {
		
		Field v = getFieldChecked( name, Type.STRING );
		
		v.value = value;
	}
	public void setLong( String name, Long value ) throws NotFoundException, WrongTypeException {
		
		Field v = getFieldChecked( name, Type.INTEGER );
		
		v.value = value;
	}
	public void setDouble( String name, Double value ) throws NotFoundException, WrongTypeException {
		
		Field v = getFieldChecked( name, Type.FLOAT );
		
		v.value = value;
	}
	public void setBoolean( String name, Boolean value ) throws NotFoundException, WrongTypeException {
		
		Field v = getFieldChecked( name, Type.BOOLEAN );
		
		v.value = value;
	}
	public void setData( String name, byte[] value ) throws NotFoundException, WrongTypeException {
		
		Field v = getFieldChecked( name, Type.DATA );
		
		v.value = value;
	}
	
	
	public Object get( String name ) throws NotFoundException {
		
		return getField( name ).value;
	}
	
	public String getString( String name ) throws NotFoundException, WrongTypeException {
		
		Field v = getFieldChecked( name, Type.STRING );
		
		return (String)v.value;
	}
	public Long getLong( String name ) throws NotFoundException, WrongTypeException {
		
		Field v = getFieldChecked( name, Type.INTEGER );
		
		return (Long)v.value;
	}
	public Double getDouble( String name ) throws NotFoundException, WrongTypeException {
		
		Field v = getFieldChecked( name, Type.FLOAT );
		
		return (Double)v.value;
	}
	public Boolean getBoolean( String name ) throws NotFoundException, WrongTypeException {
		
		Field v = getFieldChecked( name, Type.BOOLEAN );
		
		return (Boolean)v.value;
	}
	public byte[] getData( String name ) throws NotFoundException, WrongTypeException {
		
		Field v = getFieldChecked( name, Type.DATA );
		
		return (byte[])v.value;
	}
	
	
	// --- Serialization ----------------------------------------------------

	public void deserialize( String data ) throws CodingException {
		
		// append \n\n so that there is at least one more run below
		BufferedReader reader = new BufferedReader( new StringReader( data ) );
		
		String line, lastLine=null;
		int linum=0;
		try {
			
			try {
				while( ( line = reader.readLine() ) != null ) {
					
					linum++;
					
					// skip empty lines
					if( line.length() == 0 ) {
						
						continue;
						
					// append Lines starting with space
					} else if( line.charAt( 0 ) == ' ' ){
						
						if( lastLine == null )
							throw new CodingException( "Continued line (beginning with ' ') but no line to continue" );
						
						lastLine += line.substring( 1 );
						
					// process Lines
					} else {
						
						if( lastLine != null ) decodeLine( lastLine );
						
						lastLine = line;
					}
				}
				
				// finally last line :)
				if( lastLine != null ) decodeLine( lastLine );
				
				
			} catch( WashException e ) {
				
				throw new CodingException( "Error decoding ", e );
				
			} catch( IOException e ) {
				
				throw new CodingException( "IO Error" );
			}
			
		} catch( CodingException e ) {
			
			// Recatch CodingException and add line number. Then throw again
			throw new CodingException( e.getMessage() + " on line " + linum, e );
		}
	}
	
	private void decodeLine( String line ) throws WashException {
	
		int i = line.indexOf( '=' );
		
		// = not there or as first or last character is definively wrong
		if( i < 1 || ( i == line.length() -1) )
			throw new CodingException( "Wrong format" );
		
		String name = line.substring( 0, i );
		String value = line.substring( i+1 );
		
		TypeValue tv = WashTools.decodeValue( value );
		
		addField( name, tv.type, tv.value );
		
	}
	
	public String serialize(){
		
		StringBuilder result = new StringBuilder();
		
		for( Field f : indexedFields ) {
			
			if( f.value == null ) {
				
				result.append( f.name + "=NULL\n" );
				continue;
			}
			
			switch( f.type ) {
			
			case STRING:
				result.append( f.name + "=\"" + WashTools.encodeString( (String)f.value ) + "\n" );
				break;
				
			case INTEGER:
				result.append( f.name + "=i" + WashTools.encodeLong( (Long) f.value ) + "\n" );
				break;
				
			case FLOAT:
				result.append( f.name + "=f" + WashTools.encodeDouble( (Double) f.value ) + "\n" );
				break;
				
			case BOOLEAN:
				result.append( f.name + "=b" + WashTools.encodeBoolean( (Boolean) f.value ) + "\n" );
				break;
			
			case DATA:
				result.append( f.name + "=d" + WashTools.encodeData( (byte[])f.value ) );
				break;
			}
		}
		
		return result.toString();
	}

	@Override
	public JSONObject getJSON() throws NotFoundException, WrongTypeException, JSONException {
		/*
		if( ! this.getFieldNames().isEmpty() ){
			JSONObject job = new JSONObject();
			//JSONArray ja = new JSONArray();
			//job.put( "ITEMS", ja );
			
			for( int i = 0; i < indexedFields.size(); i++ ) {
				
				Field f = indexedFields.get( i );
				
				if( ! f.name.contains( "-" ) ){

					job.put( f.name, f.value );

				}else{
				
					String[] poistr = f.name.split( "\\." );
					LinkedList<String> list = new LinkedList<String>();
					String listname = "";
					HashMap<String, Object> parameters = null;
					
					for( int u = 0; u < poistr.length ; u++ ){
	
						if( ! poistr[u].contains( "-" ) ){
							listname = listname.concat( "."+poistr[u] );
						}else{
							parameters = new HashMap<String,Object>();
							parameters.put( poistr[u], f.value );
						}
					}
					listname = listname.replaceFirst( "\\.", "" );
					System.out.println( listname+" "+parameters.toString() );
					
				
					
					String[] strs = f.name.split( "-" );
					
					int num = new Integer ( strs[ strs.length-1 ] );
					
					
					if( ja.length() == num ){
						ja.put( num, new JSONObject() );
					}
					JSONObject jo = (JSONObject) ja.get( num );
					jo.put( f.name.split( "-" )[0], f.value );
					
					de.uplinkgmbh.lms.user-0.name="0 name
					de.uplinkgmbh.lms.user-0.alter=i20
					de.uplinkgmbh.lms.user-0.key-0.nr="sad2320
					de.uplinkgmbh.lms.user-0.key-1.nr="3edew0
					de.uplinkgmbh.lms.user-0.key-0.type="typal
					de.uplinkgmbh.lms.user-0.key-1.type="typal2
					de.uplinkgmbh.lms.user:{"name":"0 name","alter":20,"key":[{"nr":"sad2320","type":"typal"},{"nr":"3edew0","type":"typal2"}]}
					
				}
			}
			
			return job;
		}else{
			return new JSONObject();
		}
		*/
		return null;
	} 
	
}
